const path = require('path')
const fse = require('fs-extra')
const multiparty = require('multiparty')
const { IncomingMessage, ServerResponse } = require('http')

/** 大文件存储路径 */
const UPLOAD_PATH = path.resolve(__dirname, '../upload')

/** 提取后缀名 */
function extractExt(filename) {
  return filename.slice(filename.lastIndexOf("."), filename.length);
}

/** 获取临时文件夹路径 */
function getChunkDir(fileHash) {
  return path.resolve(UPLOAD_PATH, `chunkDir_${fileHash}`)
}

/**
 * 获取请求传递的数据
 * @param {IncomingMessage} req 
 */
function onGetRequestData(req) {
  return new Promise(resolve => {
    let chunk = ''
    req.on('data', data => {
      chunk += data
    })
    req.on('end', () => {
      resolve(JSON.parse(chunk))
    })
  })
}

/**
 * 写入文件流
 * @param {*} path 
 * @param {*} writeStream 
 */
function pipeStream(path, writeStream) {
  return new Promise((resolve => {
    const readStream = fse.createReadStream(path)
    readStream.on('end', () => {
      fse.unlinkSync(path)
      resolve()
    })
    readStream.pipe(writeStream)
  }))
}

/**
 * 合并分片文件
 * @param {String} filePath 
 * @param {String} filename 
 * @param {Number} size 
 */
async function mergeChunkFile(filePath, fileHash, size) {
  const chunkDir = path.resolve(UPLOAD_PATH, getChunkDir(fileHash))
  const chunkPaths = fse.readdirSync(chunkDir)
  chunkPaths.sort((a, b) => a.split('-')[1] - b.split('-')[1])
  await Promise.all(chunkPaths.map((chunkPath, index) => {
    return pipeStream(`${chunkDir}/${chunkPath}`, fse.createWriteStream(filePath, {
      start: index * size
    }))
  }))
  fse.rmdirSync(chunkDir)
}

/** 获取已经上传完成的文件列表 */
async function getUploadedList(fileHash) {
  const chunkPath = path.resolve(UPLOAD_PATH, getChunkDir(fileHash))
  return fse.existsSync(chunkPath) ? await fse.readdir(getChunkDir(fileHash)) : []
}

class Controller {
  /**
   * 检验文件是否已经上传了
   * @param {IncomingMessage} req 
   * @param {ServerResponse<IncomingMessage> & { req: IncomingMessage }} res 
   */
  async verifyUploadFile(req, res) {
    const { filename, fileHash } = await onGetRequestData(req)
    const ext = extractExt(filename)
    const filePath = path.resolve(UPLOAD_PATH, `${fileHash}${ext}`)
    if (fse.existsSync(filePath)) {
      res.end(JSON.stringify({
        shouldUpload: false
      }))
    } else {
      res.end(JSON.stringify({
        shouldUpload: true,
        uploadedList: await getUploadedList(fileHash)
      }))
    }
  }

  /**
   * 上传文件
   * @param {IncomingMessage} req 
   * @param {ServerResponse<IncomingMessage> & { req: IncomingMessage }} res 
   */
  async uploadFile(req, res) {
    const multipartForm = new multiparty.Form()
    multipartForm.parse(req, (err, fields, files) => {
      if (err) {
        console.log(err, 'err')
        res.statusCode = 500
        res.end(JSON.stringify({ errCode: -1, errMsg: '上传失败' }))
        return
      }

      const { chunk: [chunkBlob] } = files
      const { chunkHash: [chunk_hash], fileHash: [file_hash], name: [filename] } = fields
      // 创建临时文件夹用于临时存储chunk
      const chunkDir = path.resolve(UPLOAD_PATH, getChunkDir(file_hash))
      if (!fse.existsSync(chunkDir)) {
        fse.mkdirSync(chunkDir)
      }

      fse.moveSync(chunkBlob.path, `${chunkDir}/${chunk_hash}`)
      res.end(JSON.stringify(true))
    })
  }

  /**
   * 合并文件
   * @param {IncomingMessage} req 
   * @param {ServerResponse<IncomingMessage> & { req: IncomingMessage }} res 
   */
  async mergeFile(req, res) {
    const data = await onGetRequestData(req)
    const { filename, fileHash, size } = data
    const filePath = path.resolve(UPLOAD_PATH, `${fileHash}${extractExt(filename)}`)
    await mergeChunkFile(filePath, fileHash, size)
    res.end(JSON.stringify(true))
  }
}

module.exports = Controller